
#include "common.h"
#include "sock.h"


static void sock_handle_write(int fd, short flag, void *arg);
static void sock_handle_sendfile(int fd, short flag, void *data);
static void mem_write_state_free(mem_write_state *mws);
static void sendfile_state_free(sendfile_state *ss);
static void sock_handle_connect(int fd, short flag, void *arg);
static void connect_state_clean(connect_state_t *cs);
static void sock_handle_read(int fd, short flag, void *arg);
static void read_state_free(read_state_t *rs);

int sock_open(int reuse, int noblock)
{
	int fd = -1;
	if(-1 == (fd = socket(AF_INET, SOCK_STREAM, 0))) {
		error_log(errno, "open sockect %d failed\n", fd);
	}
	
	if(reuse)
		sock_reuseaddr(fd);
	if(noblock)
		sock_noblocking_set(fd);
	return fd;
}

void sock_close(int fd)
{
	if(fd < 0)
		return;
	close(fd);
	debug_log(3, "fd %d closed\n", fd);
}

void sock_listen(int fd, int queue)
{
	if(fd < 0) {
		debug_log(1, "fd %d listen failed\n", fd);
		return;
	}

	if(listen(fd, queue) == -1) {
		error_log(errno, "listen %d failed\n", fd);
		return;
	}

	debug_log(1, "fd %d listen, backlog %d\n", fd, queue);
}

int sock_bind(int fd, struct sockaddr *addr, int addr_size)
{
	if(fd < 0) {
		debug_log(1, "fd %d < 0\n", fd);
		return -1;
	}
	if(-1 == (bind(fd, addr, addr_size))) {
		error_log(errno,"bind %d failed. Exit\n", fd);
		return -1;
	}
	
	debug_log(1, "fd %d bind %012X\n", fd, ((struct sockaddr_in *)addr)->sin_addr);
	return 0;
}

int sock_accept(int fd, struct sockaddr *addr, int size)
{
	if(fd < 0) 
		return -1;

	int c_fd = accept(fd, addr, (socklen_t *)&size);
	if(-1 == c_fd) {
		debug_log(2, "%d accept failed\n", fd);
		return -1;
	}
	
	debug_log(3, "accept %d connection\n", c_fd);
	return c_fd;
}

int error_ignore(int error_no) 
{         
	switch (error_no) {
		case EINPROGRESS:
		case EAGAIN:
		case EINTR: 
			debug_log(5, "ignore %d errno\n", error_no);
			return 1;
		default:
			debug_log(5, "error happened %d\n", error_no);
			return 0;
	}
	/* NOTREACHED */
}

int sock_noblocking_set(int fd)
{         
	int flag;
	int dummy = 0;

	if(-1 == (flag = fcntl(fd, F_GETFL, dummy))) {
		error_log(errno, "fcntl %d failed\n", fd);
		return -1; 
	}   

	if(-1 == (flag = fcntl(fd, F_SETFL, dummy | O_NONBLOCK))) {
		error_log(errno, "fcntl %d failed\n", fd);
		return -1;
	}
	debug_log(8, "fd %d noblocking set\n", fd);
	return 0;
}

int sock_blocking_set(int fd) 
{
	int flag;
	int dummy = 0;

	if(-1 == (flag = fcntl(fd, F_GETFL, dummy))) {
		error_log(errno, "fcntl failed\n");
		return -1;
	}
	if(-1 == (flag = fcntl(fd, F_SETFL, flag & ~O_NONBLOCK))) {
		error_log(errno, "fcntl %d failed\n", fd);
		return -1;
	}
	debug_log(8, "fd %d blocking set\n", fd);
	return 0;
}

void sock_write(event_t *ev, int fd, WPF *handler, void *arg, GString *s)
{
	if(!ev) 
		return;
	mem_write_state *mws ;

	mws = g_slice_new(mem_write_state);
	assert(mws);
	mws->buf =  (char *)g_strndup(s->str, s->len);
	mws->size = s->len;
	mws->offset = 0;
	mws->handler = handler;
	mws->arg = arg;
	mws->ev = ev;
	if( -1 == xevent_update(ev, SOCK_FLAG_WRITE, mws, sock_handle_write)) {
		error_log(errno, "fd %d event update failed\n", fd);
		return;
	}
	debug_log(9, "fd %d, offset %d, size %d, buf %p\n", \
					fd, mws->offset, mws->size, mws->buf);
}


static void sock_handle_write(int fd, short flag, void *arg)
{
	assert(arg);

	mem_write_state *mws = arg;
	int sz;
	int sz_need;
	int error = 0;
	
	sz_need = mws->size - mws->offset;
	debug_log(9, "fd %d, offset %d, size %d, buf %p\n", \
					fd, mws->offset, mws->size, mws->buf);
	if(sz_need == 0) {
		WPF *cb = mws->handler;
		int offset = mws->offset;
		void *arg = mws->arg;
		mem_write_state_free(mws);
		if(cb) {
			cb(fd, error, offset, arg);
		}
		return;
	}
	sz = write(fd, mws->buf + mws->offset, sz_need);
	if(sz == -1) {
		if(error_ignore(errno))
			return;
		error = 1;
		WPF *cb = mws->handler;
		int offset = mws->offset;
		void *arg = mws->arg;
		mem_write_state_free(mws);
		if(cb) {
			cb(fd, error, offset, arg);
		}
		return;
		
	}
	mws->offset += sz;
	if(mws->offset >= mws->size) {
		WPF *cb = mws->handler;
		int offset = mws->offset;
		void *arg = mws->arg;
		mem_write_state_free(mws);
		if(cb) {
			cb(fd, error, offset, arg);
		}
		return;
	}
}

void sock_reuseaddr(int fd)
{
	int on = 1;
	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)) < 0)
		error_log(errno, "FD %d set reuseaddr failed\n", fd);
}

static void mem_write_state_free(mem_write_state *mws)
{
	if(!mws) 
		return;

	event_del(mws->ev);
	safe_free(mws->buf);
	g_slice_free(mem_write_state, mws);
}

void sock_sendfile(event_t *ev, int in, int out, int offset, int size, WPF *callback, void *data)
{
	sendfile_state *ss = g_slice_new(sendfile_state);
	
	assert(ss);
	ss->in_fd = in;
	ss->out_fd = out; 
	ss->offset = offset; 
	ss->size = size;
	ss->size_send = 0;
	ss->callback = callback;
	ss->data = data;
	ss->ev = ev;
	
	debug_log(9, "out_fd %d, in_fd %d, size  %d\n", \
				ss->out_fd, ss->in_fd, ss->size);
	if(xevent_update(ev, SOCK_FLAG_WRITE, ss, sock_handle_sendfile) == -1) {
		error_log(errno, "fd %d event update failed\n", out);
		return;
	}
}

static void sock_handle_sendfile(int fd, short flag, void *data)
{
	assert(data);
	int error = 0;
	sendfile_state *ss = data;
	int size_need = ss->size - ss->size_send;
	int res;
	off_t size_write = -1;
	
	debug_log(9, "out_fd %d, in_fd %d, offset %d, size_need %d\n", \
				ss->out_fd, ss->in_fd, ss->offset, size_need);
#if defined(HAVE_SYS_SENDFILE_H) && !defined(HAVE_SYS_UIO_H)
	size_write = sendfile(ss->out_fd, ss->in_fd, (off_t *)&ss->offset, size_need);
	if(size_write == -1) {
		if(error_ignore(errno))  {
			return;
		}
		else {
			error_log(errno, "fd %d sendfile failed\n", fd);
			error = 1;
			WPF *handle = ss->callback;
			void *handle_data = ss->data;
			res = ss->size_send;
			sendfile_state_free(ss);
			handle(fd, error, res, handle_data);
			return;
		}
	}
#elif defined(HAVE_SYS_UIO_H) && !defined(HAVE_SYS_SENDFILE_H)
	if(-1 == sendfile(ss->in_fd, ss->out_fd, \
							(off_t )ss->offset, size_need, NULL, &size_write, 0)) {
		if(error_ignore(errno))
			return;
		else {
			error_log(errno, "fd %d sendfile failed\n", fd);
			error = 1;
			WPF *handle = ss->callback;
			void *handle_data = ss->data;
			res = ss->size_send;
			sendfile_state_free(ss);
			handle(fd, error, res, handle_data);
			return;
		}
	}
	ss->offset += size_write;
#endif

	ss->size_send += size_write;
	if(ss->size_send < ss->size) {
		return;
	}
	else {
		WPF *handle = ss->callback;
		res = ss->size_send;
		void *handle_data = ss->data;
		sendfile_state_free(ss);
		handle(fd, error, res, handle_data);
	}
}

void sock_cork_set(int fd)
{
	int on = 1; 
#if defined(TCP_CORK) && ! defined(TCP_NOPUSH)	
	if(-1 == setsockopt(fd, IPPROTO_TCP, TCP_CORK, (char *)&on, sizeof(on))) {
		error_log(errno, "fd %d cork set failed", fd);
	}
#elif defined(TCP_NOPUSH) && !defined(TCP_CORK)
	if(-1 == setsockopt(fd, IPPROTO_TCP, TCP_NOPUSH, (char *)&on, sizeof(on))) {
		error_log(errno, "fd %d cork set failed", fd);
	}
#endif
}

static void sendfile_state_free(sendfile_state *ss)
{
	if(!ss)
		return;

	event_del(ss->ev);
	g_slice_free(sendfile_state, ss);
}

void sock_connect(event_t *ev, int fd, struct sockaddr_in *remote,\
			   	void *data, void (*cb) (int, int, int, void *))
{
	assert(ev);
	if(fd < 0 || !remote) 
		return;

	int res;
	connect_state_t *cs;
	
	cs = g_slice_new(connect_state_t);
	assert(cs);
	cs->fd = fd;
	cs->ev = ev;
	cs->retry = 0;
	cs->cb = cb;
	cs->cb_data = data;

	debug_log(5, "fd %d, retry %d, connecting...\n", cs->fd, cs->retry);
	if(0 == (res = connect(fd, (struct sockaddr *)remote, sizeof(*remote)))) {
		sock_handle_connect(fd, SOCK_FLAG_WRITE, cs);
		return;
	}
	else if(res < 0) {
		if(!error_ignore(errno)) {
			error_log(errno, "fd %d connecting failed\n", fd);
			sock_handle_connect(fd, SOCK_FLAG_NONE, cs);
		}
		else {	
			xevent_update(cs->ev, SOCK_FLAG_WRITE, cs, sock_handle_connect);
		}
		return;
	}
	
}

static void sock_handle_connect(int fd, short flag, void *arg)
{
	int err;
	int error = 0;
	size_t errlen = sizeof(err);
	connect_state_t *cs = arg;
	void (*cb) (int, int, int, void *data);
	void *data = NULL;
	int res = 0;

	debug_log(5, "fd %d, flag %d, arg %p\n", fd, flag, arg);
	cb = NULL;
	if(cs) {
		res = cs->retry;
		cb = cs->cb;
		data = cs->cb_data;
	}
	if(flag == SOCK_FLAG_NONE) {
		error = 1;
		goto connect_finish;
	}
	if(0 != getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen)) {
		error_log(errno, "fd %d connect failed\n", fd);
		error = 1;
		goto connect_finish;
	}
	if(err != 0) {
		error_log(errno, "fd %d connect failed\n", fd);
		error = 1;
		goto connect_finish;
	}

connect_finish:
	connect_state_clean(cs);
	if(cb) 
		cb(fd, error, res, data);
	else
		debug_log(5, "callback function is %p\n", cb);
}

static void connect_state_clean(connect_state_t *cs)
{
	if(!cs)
		return;

	event_del(cs->ev);
	g_slice_free(connect_state_t, cs);
}

int sock_open_from_host(const char *host, struct in_addr *addr)
{
	int h_error;
	char buf[1024];
	struct hostent *srv_host, host_info;
	int server_fd = sock_open(1, 1);
	char server_ip[16];

	if(server_fd == -1) {
		error_log(errno, "fd %d, failed to open new socket\n", server_fd);
		return -1;
	}

	if(0 != gethostbyname_r((char *)host, &host_info, buf, sizeof(buf), &srv_host, &h_error )) {
		debug_log(1, "%s host search failed. {%s}\n", host, strerror(h_error));
		sock_close(server_fd);
		return -1;
	}
	if(srv_host == NULL) {
		debug_log(1, "host %s gethostbyname failed. {%s}\n", \
					   	host, strerror(h_error));
		sock_close(server_fd);
		return -1;
	}
	if(NULL == (char *)inet_ntop(AF_INET, srv_host->h_addr, server_ip, 16)) {
		error_log(errno, "fd %d inet_ntop failed\n", server_fd);
		sock_close(server_fd);
		return -1;
	}
	debug_log(5, "host %s(%s)\n", host, server_ip);
		
	if(inet_pton(AF_INET, server_ip, addr) < 0) {
		error_log(errno, "fd %d inet_pton failed\n", server_fd);
		sock_close(server_fd);
		return -1;
	}
	
	debug_log(3, "success create %d\n", server_fd);
	return server_fd;
}

int sock_ip_port_parse(const char *src, char *ip, int *port)
{
	if(!src)
		return -1;
	if(!ip)
		return -1;

	int ip_len = 0;
	char *delim = NULL;

	delim = strchr(src, ':');
	if(!delim)
		return -1;
	ip_len = delim - src;
	*port = atoi(delim + 1);
	strncpy(ip, src, ip_len);
	ip[ip_len] = '\0';
	debug_log(5, "src %s, ip %s, port %d\n", src, ip, *port);
	return 0;
}

void sock_read(event_t *ev, int fd, \
				int (*filter)(const char *, const int, void *), void *arg)
{
	read_state_t *rs = g_slice_new(read_state_t);
	
	rs->filter = filter;
	rs->ev = ev;
	rs->fd = fd;
	rs->arg = arg;

	xevent_update(ev, SOCK_FLAG_READ, rs, sock_handle_read);
}

static void sock_handle_read(int fd, short flag, void *arg)
{
	char buf[MAX_BUFFER];
	int size;
	int (*filter)(const char *, const int, void *);
	void *data;
	read_state_t *rs = arg;

	assert(rs);
	filter = rs->filter;
	data = rs->arg;
	size = read(fd, buf, MAX_BUFFER);
	if(-1 == size) {
		error_log(errno, "failed to read %d\n", fd);
		read_state_free(rs);
		filter(NULL, 0, data);
		return;
	}
	else if(size == 0) {
		/* server close */
		read_state_free(rs);
		filter(NULL, 0, data);
		return;
	}
	filter(buf, size, data);
}

static void read_state_free(read_state_t *rs)
{
	if(!rs)
		return;
	xevent_del(rs->ev);
	g_slice_free(read_state_t, rs);
}
